
class MVVM{
    $el:any;
    $data:any;
    constructor(options){
        this.$el = options.el;
        this.$data = options.data;
        if(this.$el){
            //1.数据劫持
            new Observer(this.$data);
            console.log(this.$data);
            //2.模板编译
            new Compile(this.$el,this);
        }
    }
}

class Dep{
    watchers:any[];
    static target:any;
    constructor(){
        this.watchers = [];
    }

    add(watcher){ //watcher的实例
        this.watchers.push(watcher);
    }

    notify(){
        this.watchers.forEach(watcher => {
            watcher.update();
        })
    }
}

//订阅  观察者

/**
 * new Wacther(vm,expr,(newValue) => {
            //更新视图
            fn(node,newValue);
        })
 * */ 

class Wacther{
    vm:any;
    expr:string;
    cb:any;
    oldValue:any;
    constructor(vm,expr,cb){
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        //旧值
        this.oldValue = this.get();
    }

    get(){
        Dep.target = this;//把watacher的实例存在Dep.target
        let value = compileUtil.getVal(this.expr,this.vm);
        Dep.target = null;
        return value
    }

    update(){
        //获取新值
        let newValue = compileUtil.getVal(this.expr,this.vm);
        if(newValue != this.oldValue){
            this.cb(newValue);
        }
    }
}

//vm.$watch(vm,'user.username',(newVal) => {})

//数据劫持

class Observer{
    constructor(data){
        this.observer(data);
    }

    observer(data){
        //判断数据是否存在并且是对象
        if(data && typeof data === 'object'){
            // 对每一个属性进行劫持
            /**
             * {
                user:{
                    username:'lili',
                    age:18
                },
                info:'1709A'
            }
             * 
            */
            for(let key in data){
                this.observerData(data,key,data[key]);
            }
        }
    }

    //对属性进行劫持
    observerData(data:any,key:string,value:any){
        this.observer(value);
        let dep = new Dep();
        //对象  属性  set get
        Object.defineProperty(data,key,{
            set(newValue){
                if(newValue != value){
                    console.log("设置值")
                    value = newValue;
                    dep.notify();
                }
            },
            get(){
                Dep.target && dep.add(Dep.target); //watcher实例
                console.log("获取值")
                return value
            }
        })
    }
}

//编译  模板+数据  进行视图的初始化

class Compile{
    
    constructor(el,vm) {
        this.vm = vm;  //vm.$data数据
        this.el = this.isElementNode(el) ? el : document.querySelector(el); //模板

        //把元素放到内存中
        let fragment = this.nodeToElement(this.el);

        console.log(fragment)

        //编译过程
        this.compile(fragment);

        //放到页面
        this.el.appendChild(fragment)
    }

    //属性是否是指令  v-
    isDirective(attr){
        return attr.startsWith('v-');
    }

    //编译元素
    compileElement(node){
        let attributes = node.attributes;
        // console.log(attributes);
        [...attributes].forEach(attr => {
            // console.dir(attr);
            let {name,value:expr} = attr;
            if(this.isDirective(name)){  
                //指令  html text model
                let [,directive] = name.split('-');
                compileUtil[directive](node,expr,this.vm);
            }
        })
        
    }
    
    //编译文本
    compileText(node){
        let content = node.textContent;
        if(/\{\{(.+?)\}\}/.test(content)){
            compileUtil.content(node,content,this.vm);
        }
    }

    //编译
    compile(node){
        let childNodes = node.childNodes;
        //childNodes只取子节点
        // console.log([...childNodes]);
        [...childNodes].forEach(child => {
            if(this.isElementNode(child)){
                //区分是元素节点
                this.compileElement(child);
                this.compile(child);
            }else{
                 //区分是文本节点
                 this.compileText(child);
            }
        })
    }

    //编译的方法

    nodeToElement(node){
        //创建文档片段
        let fragment = document.createDocumentFragment();
        let firstChild;
        while(firstChild = node.firstChild){
            fragment.appendChild(firstChild);
        }
        return fragment
    }

    //判断是否是元素节点
    isElementNode(node){
        return node.nodeType === 1
    }
    
}

let compileUtil = {
    //根据表达式从vm.$data上获取指定的数据  
    getVal(expr,vm){  //user.username
        // [user,username]

        /**
         * {
                user:{
                    username:'lili',
                    age:18
                },
                info:'1709A'
            }
         * 
        */
        return expr.split('.').reduce((pre,current) => {
            return pre[current]
        },vm.$data)
    },
    //设置表达式的值
    setVal(vm,expr,value){ //expr:   [user,username]
        expr.split('.').reduce((data,current,index,arr) => {
            if(index === arr.length-1){
                return data[current] = value
            }
            return data[current]
        },vm.$data)
    },
    model(node,expr,vm){
        let value = this.getVal(expr,vm);
        let fn = this.update.compileModel;
        //生成观察者
        new Wacther(vm,expr,(newValue) => {
            //更新视图
            fn(node,newValue);
        })
        fn(node,value);
        //给指定节点添加input事件，给指定数据修改值
        node.addEventListener('input',(e) => {
            console.log(e.target.value)
            // e.target.value  ===> vm.$data.user.username
            this.setVal(vm,expr,e.target.value);
        })
    },
    content(node,content,vm){ //{{user.username}}
        let value = content.replace(/\{\{(.+?)\}\}/g,(...argv) => {
            console.log(argv)
            let value = this.getVal(argv[1],vm);
            //生成观察者
            new Wacther(vm,argv[1],(newValue) => {
                //更新视图
                fn(node,newValue);
            })
            return value
        })
        
        let fn = this.update.compileContent;
        fn(node,value);
    },
    html(node,expr,vm){
        let value = this.getVal(expr,vm);
        let fn = this.update.compileHtml;
        new Wacther(vm,expr,(newValue) => {
            //更新视图
            fn(node,newValue);
        })
        fn(node,value);
    },
    update:{
        compileModel(node,value){
            node.value = value;
        },
        compileHtml(node,value){
            node.innerHTML = value;
        },
        compileContent(node,value){
            node.textContent = value;
        }
    }
}
